package com.atguigu.tingshu.account.service.impl;
import java.util.Date;

import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.account.mapper.RechargeInfoMapper;
import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.model.account.RechargeInfo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Maps;

import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.account.UserAccount;
import com.atguigu.tingshu.model.account.UserAccountDetail;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.parameters.P;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {

	@Autowired
	private UserAccountMapper userAccountMapper;

	@Autowired
	private UserAccountDetailMapper userAccountDetailMapper ;

	@Autowired
	private RedisTemplate<String , String> redisTemplate ;

	@Autowired
	private RechargeInfoMapper rechargeInfoMapper ;

	/**
	 * 需要保证接口的幂等性，由于该接口获取到数据以后，直接操作数据库。因此要保证接口的幂等性可以直接从数据库层面进行考虑
	 * 给user_id字段添加唯一约束
	 * @param userId
	 */
	@Transactional
	@Override
	public void initAccount(long userId) {
		UserAccount userAccount = new UserAccount() ;
		userAccount.setUserId(userId);
		userAccountMapper.insert(userAccount) ;
	}

	@Override
	public BigDecimal getAvailableAmount() {

		// 根据当前登录用户的id查询账户的可用金额
		Long userId = AuthContextHolder.getUserId();
		LambdaQueryWrapper<UserAccount> lambdaQueryWrapper = new LambdaQueryWrapper<>() ;
		lambdaQueryWrapper.eq(UserAccount::getUserId , userId) ;
		UserAccount userAccount = userAccountMapper.selectOne(lambdaQueryWrapper);
		return userAccount.getAvailableAmount();

	}

	@Transactional
    @Override
    public AccountLockResultVo checkAndLock(AccountLockVo accountLockVo) {

		/**
		 * 需要保证当前接口的幂等性， 可以使用Redis中的setnx命令保证
		 */
		String orderNo = accountLockVo.getOrderNo();
		String idempotentKey = "account:lock:idempotent:" + orderNo ;
		String accountLockResultKey  = "account:lock:result:" + orderNo ;
		Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(idempotentKey, orderNo, 10, TimeUnit.MINUTES);
		if(!ifAbsent) {		// 是重复请求
			String accountLockResult = redisTemplate.opsForValue().get(accountLockResultKey);
			if(!StringUtils.isEmpty(accountLockResult)) {
				return JSON.parseObject(accountLockResult , AccountLockResultVo.class) ;
			}else {
				throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_REPEAT) ;
			}
		}

		// 检查账户的可用金额
		Long userId = accountLockVo.getUserId();
		BigDecimal amount = accountLockVo.getAmount();
		String content = accountLockVo.getContent();
		UserAccount userAccount = userAccountMapper.checkAmount(userId , amount) ;
		if(userAccount == null) {		// 可用余额不够的
			redisTemplate.delete(idempotentKey) ;
			throw new GuiguException(ResultCodeEnum.ACCOUNT_LESS) ;
		}

		// 锁定账户的可用金额
		int count = userAccountMapper.lockAmount(userId , amount) ;			// 该接口方法执行完毕以后，返回的int类型的数据表示的就是影响的数据行
		if(count == 0) {		// 锁定账户进行失败了
			redisTemplate.delete(idempotentKey) ;
			throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_ERROR) ;
		}

		// 记录账户变更的日志数据
		this.log(userId , "锁定:" + content , SystemConstant.ACCOUNT_TRADE_TYPE_LOCK , amount , orderNo);

		// 构建响应结果数据
		AccountLockResultVo accountLockResultVo = new AccountLockResultVo();
		accountLockResultVo.setUserId(userId);
		accountLockResultVo.setAmount(amount);
		accountLockResultVo.setContent(content);

		// 把锁定结果存储起来
		redisTemplate.opsForValue().set(accountLockResultKey , JSON.toJSONString(accountLockResultVo) , 10 , TimeUnit.MINUTES );

		// 返回
		return accountLockResultVo;
    }

	@Override
	public void miunsAccount(String orderNo) {

		// 扣减账户的金额
		String idempotentKey = "account:minus:idempotent:" + orderNo ;
		Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(idempotentKey, orderNo, 10, TimeUnit.MINUTES);
		if(!ifAbsent) {
			return;
		}

		String accountLockResultKey  = "account:lock:result:" + orderNo ;
		String accountLockResultVoJSON = redisTemplate.opsForValue().get(accountLockResultKey);
		if(StringUtils.isEmpty(accountLockResultVoJSON)) return ;

		AccountLockResultVo accountLockResultVo = JSON.parseObject(accountLockResultVoJSON, AccountLockResultVo.class);
		Long userId = accountLockResultVo.getUserId();
		BigDecimal amount = accountLockResultVo.getAmount();
		int count = userAccountMapper.miunsAccount(userId , amount) ;
		if(count == 0) {
			redisTemplate.delete(idempotentKey) ;
			throw new GuiguException(ResultCodeEnum.ACCOUNT_MINUSLOCK_ERROR);
		}

		// 记录账户变更的日志数据
		this.log(userId , "扣减:" + accountLockResultVo.getContent() , SystemConstant.ACCOUNT_TRADE_TYPE_MINUS , amount , orderNo);

		// 从Redis中删除锁定记录
		redisTemplate.delete(accountLockResultKey) ;

	}

	@Override
	public void unLockAccount(String orderNo) {

		// 扣减账户的金额
		String idempotentKey = "account:unlock:idempotent:" + orderNo ;
		Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(idempotentKey, orderNo, 10, TimeUnit.MINUTES);
		if(!ifAbsent) {
			return;
		}

		String accountLockResultKey  = "account:lock:result:" + orderNo ;
		String accountLockResultVoJSON = redisTemplate.opsForValue().get(accountLockResultKey);
		if(StringUtils.isEmpty(accountLockResultVoJSON)) return ;

		AccountLockResultVo accountLockResultVo = JSON.parseObject(accountLockResultVoJSON, AccountLockResultVo.class);
		Long userId = accountLockResultVo.getUserId();
		BigDecimal amount = accountLockResultVo.getAmount();
		int count = userAccountMapper.unlockAccount(userId , amount) ;
		if(count == 0) {
			redisTemplate.delete(idempotentKey) ;
			throw new GuiguException(ResultCodeEnum.ACCOUNT_UNLOCK_ERROR);
		}

		// 记录账户变更的日志数据
		this.log(userId , "解锁:" + accountLockResultVo.getContent() , SystemConstant.ACCOUNT_TRADE_TYPE_UNLOCK , amount , orderNo);

		// 从Redis中删除锁定记录
		redisTemplate.delete(accountLockResultKey) ;

	}

	@Transactional
	@Override
	public void rechargeInfoPaySuccess(String orderNo) {

		// 修改充值记录的支付状态为已支付
		LambdaQueryWrapper<RechargeInfo> rechargeInfoLambdaQueryWrapper = new LambdaQueryWrapper<>() ;
		rechargeInfoLambdaQueryWrapper.eq(RechargeInfo::getOrderNo , orderNo) ;
		RechargeInfo rechargeInfo = rechargeInfoMapper.selectOne(rechargeInfoLambdaQueryWrapper);
		if(SystemConstant.ORDER_STATUS_PAID.equals(rechargeInfo.getRechargeStatus())) return ;

		// 更新支付状态
		rechargeInfo.setRechargeStatus(SystemConstant.ORDER_STATUS_PAID);
		rechargeInfoMapper.updateById(rechargeInfo) ;

		// 更新账户的金额
		Long userId = rechargeInfo.getUserId();
		LambdaQueryWrapper<UserAccount> lambdaQueryWrapper = new LambdaQueryWrapper<>() ;
		lambdaQueryWrapper.eq(UserAccount::getUserId , userId) ;
		UserAccount userAccount = userAccountMapper.selectOne(lambdaQueryWrapper);
		userAccount.setAvailableAmount(userAccount.getAvailableAmount().add(rechargeInfo.getRechargeAmount()));
		userAccount.setTotalAmount(userAccount.getTotalAmount().add(rechargeInfo.getRechargeAmount()));
		userAccount.setTotalIncomeAmount(userAccount.getTotalIncomeAmount().add(rechargeInfo.getRechargeAmount()));
		userAccountMapper.updateById(userAccount) ;

		// 记录账户变更的日志数据
		this.log(userId , "充值" + rechargeInfo.getRechargeAmount() , SystemConstant.ACCOUNT_TRADE_TYPE_DEPOSIT ,
				rechargeInfo.getRechargeAmount() , orderNo);

	}

	@Override
	public IPage<UserAccountDetail> findUserRechargePage(Integer pageNo, Integer pageSize, String tradeType) {
		IPage<UserAccountDetail> page = new Page<>(pageNo , pageSize);
		Long userId = AuthContextHolder.getUserId();
		LambdaQueryWrapper<UserAccountDetail> lambdaQueryWrapper = new LambdaQueryWrapper<>() ;
		lambdaQueryWrapper.eq(UserAccountDetail::getUserId , userId) ;
		lambdaQueryWrapper.eq(UserAccountDetail::getTradeType , tradeType) ;
		userAccountDetailMapper.selectPage(page , lambdaQueryWrapper) ;
		return page;
	}

	// 记录账户变更的日志数据
	private void log(Long userId , String title, String tradeType, BigDecimal amount, String orderNo) {
		UserAccountDetail accountDetail = new UserAccountDetail() ;
		accountDetail.setUserId(userId);
		accountDetail.setTitle(title);
		accountDetail.setTradeType(tradeType);
		accountDetail.setAmount(amount);
		accountDetail.setOrderNo(orderNo);
		userAccountDetailMapper.insert(accountDetail) ;
	}

}
